package com.a0soft.gphone.aTruffleHog.widget;
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * Copyright(C) 2011 
 *   Sam Lu <ysl.sam.lu (at) gmail.com>
 *
 */

import java.util.ArrayList;

import com.a0soft.gphone.aTruffleHog.R;
import com.a0soft.gphone.aTruffleHog.util.MiscUtil;
import com.a0soft.gphone.aTruffleHog.util.aDebug;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class PieChartView extends View {
    public interface OnListener {
        public abstract void OnPieSelected(PieChartView vPieChart, int nPos);
    }
    private OnListener  m_listener;
    
    private Adapter m_adapter;
    private int     m_nSelectedPie;
    private int     m_nSelectedPieFor1stDown;
    private boolean m_bSelectedPieChanged;
    
    private int m_nWidgetW, m_nWidgetH;
    private int m_nCenterX, m_nCenterY;
    private int m_nRadius;
    private int m_nMargin;
    private int m_nShiftValue;
    
    private RectF m_rc;
    
    private ArrayList<Float> m_fPercentages;
    private ArrayList<Integer> m_nFillColors;
    private ArrayList<Integer> m_nBorderColors;
    private int m_nActivePieIdx;
    
    private float   m_fStartAngle;
    
    private Paint   m_paintPieFill, m_paintPieBorder, m_paintSelectedPieBorder,
                    m_paintActivePieFill;
    
    private static final String LOG_TAG = PieChartView.class.getSimpleName();
    
    /////////////////////////////////////////////////////////
    public 
    PieChartView(Context ctx, AttributeSet attrs)
    {
        super(ctx, attrs);
        
        m_fStartAngle = 0;
        
        m_nSelectedPie = -1;
        
        m_paintPieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        m_paintPieFill.setStyle(Paint.Style.FILL);
        
        m_paintPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
        m_paintPieBorder.setStyle(Paint.Style.STROKE);
        m_paintPieBorder.setStrokeWidth(MiscUtil.GetPixelFromDP(1.5f));
        
        m_paintSelectedPieBorder = new Paint(Paint.ANTI_ALIAS_FLAG);
        m_paintSelectedPieBorder.setStyle(Paint.Style.STROKE);
        m_paintSelectedPieBorder.setStrokeWidth(MiscUtil.GetPixelFromDP(3));
        m_paintSelectedPieBorder.setColor(0xD0FFFFFF);
        
        Bitmap bmp = ((BitmapDrawable)ctx.getResources()
                                         .getDrawable(R.drawable.grid_black_tile))
                         .getBitmap();
        BitmapShader bs = new BitmapShader(bmp, 
                                           android.graphics.Shader.TileMode.REPEAT,
                                           android.graphics.Shader.TileMode.REPEAT);
        m_paintActivePieFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        m_paintActivePieFill.setStyle(m_paintPieFill.getStyle());
        m_paintActivePieFill.setShader(bs);
    }
    
    public void
    SetListener(OnListener listener)
    {
        m_listener = listener;
    }
    
    @Override protected void 
    onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int nModeW = MeasureSpec.getMode(widthMeasureSpec);
        int nModeH = MeasureSpec.getMode(heightMeasureSpec);
        int nW = MeasureSpec.getSize(widthMeasureSpec);
        int nH = MeasureSpec.getSize(heightMeasureSpec);
        
        if (aDebug.ENABLED) {
            aDebug.d(LOG_TAG, "widthMeasureSpec=" + nModeW + ", " + nW);
            aDebug.d(LOG_TAG, "heightMeasureSpec=" + nModeH + ", " + nH);
        }
        
        if (nModeH == MeasureSpec.AT_MOST)
            nH = -nH;
        if (nModeW == MeasureSpec.AT_MOST)
            nW = -nW;
        
        _SizeChanged(nW, nH);
        
        setMeasuredDimension(m_nWidgetW, m_nWidgetH);
    }
    
    @Override public boolean 
    onTouchEvent(MotionEvent event)
    {
        if (m_adapter == null) return false;
        _GetData();
        
        int nAction = event.getAction();
        //aDebug.d(LOG_TAG, "nAction=" + nAction);
        
        int nSelectedPie = Integer.MIN_VALUE;
        switch (nAction) {
        case MotionEvent.ACTION_DOWN:
            m_nSelectedPieFor1stDown = m_nSelectedPie;
            m_bSelectedPieChanged = false;
            nSelectedPie = _GetPie(event.getX(), event.getY());
            break;
        case MotionEvent.ACTION_MOVE:
            nSelectedPie = _GetPie(event.getX(), event.getY());
            if (!m_bSelectedPieChanged &&
                nSelectedPie != m_nSelectedPieFor1stDown)
                m_bSelectedPieChanged = true;
            break;
        case MotionEvent.ACTION_UP:
            nSelectedPie = _GetPie(event.getX(), event.getY());
            if (!m_bSelectedPieChanged &&
                nSelectedPie == m_nSelectedPieFor1stDown) //toggle the current selected pie
                nSelectedPie = -1;
            break;
        case MotionEvent.ACTION_CANCEL:
            nSelectedPie = m_nSelectedPieFor1stDown;
            break;
        }
        
        if (nSelectedPie != Integer.MIN_VALUE)
            SetSelected(nSelectedPie, true);
        return true;
    }
    
    @Override protected void 
    onDraw(Canvas canvas)
    {
        //aTrace.DOut(LOG_TAG, "onDraw(): begin");
        super.onDraw(canvas);
        
        //canvas.drawColor(0xFF808080);
        
        if (m_adapter == null) return;
        _GetData();
        
        if (m_fPercentages == null) return;
        
        float fStartAngle = m_fStartAngle;
        float fSweepAngle;
        final int N = m_fPercentages.size();
        if (N == 1) {
            _DrawPie(canvas, fStartAngle, 360f, 
                     m_nFillColors.get(0), m_nBorderColors.get(0),
                     0 == m_nSelectedPie,
                     0 == m_nActivePieIdx);
        }
        else {
            float fStartAngleSelected = Float.NaN;
            float fSweepAngleSelected = Float.NaN;
            for (int i = 0; i < N; i++) {
                fSweepAngle = _PercentageToAngle(m_fPercentages.get(i));
                if (i != m_nSelectedPie) {
                    _DrawPie(canvas, 
                             fStartAngle, 
                             fSweepAngle, 
                             m_nFillColors.get(i), 
                             m_nBorderColors.get(i),
                             false, i == m_nActivePieIdx);
                }
                else {
                    fStartAngleSelected = fStartAngle;
                    fSweepAngleSelected = fSweepAngle;
                }
                fStartAngle = (fStartAngle + fSweepAngle) % 360;
            }
            
            if (!Float.isNaN(fStartAngleSelected)) {
                _DrawPie(canvas, 
                         fStartAngleSelected, 
                         fSweepAngleSelected, 
                         m_nFillColors.get(m_nSelectedPie), 
                         m_nBorderColors.get(m_nSelectedPie),
                         true, m_nSelectedPie == m_nActivePieIdx);
            }
        }
        //aTrace.DOut(LOG_TAG, "onDraw():end");
    }
    
    public final void
    SetSelected(int nSelectedPie, boolean bCallCallback)
    {
        if (nSelectedPie != m_nSelectedPie) {
            m_nSelectedPie = nSelectedPie;
            invalidate();
            
            if (bCallCallback && m_listener != null)
                m_listener.OnPieSelected(this, nSelectedPie);
        }
    }
    
    public final void
    SetOnListener(OnListener listener)
    {
        m_listener = listener;
    }
    
    public void
    SetAdapter(Adapter adapter)
    {
        if (m_adapter != null)
            m_adapter.m_vPieChart = null;
        
        m_adapter = adapter;
        
        if (adapter != null) {
            m_adapter.m_vPieChart = this;
        }
        
        _Init();
        
        invalidate();
    }
    
    private void
    _Init()
    {
        m_nSelectedPie = -1;
        m_fPercentages = null;
        m_nFillColors = null;
        m_nBorderColors = null;
    }
    
    private void
    _SizeChanged(int nW, int nH)
    {
        int nSize;
        if (nW < 0 && nH > 0)
            nSize = Math.min(-nW, nH);
        else if (nW > 0 && nH < 0)
            nSize = Math.min(nW, -nH);
        else if (nW < 0 && nH < 0)
            nSize = Math.min(-nW, -nH);
        else
            nSize = Math.min(nW, nH);
        
        if (nSize == m_nWidgetW && nSize == m_nWidgetH)
            return;
        
        m_nWidgetW = nSize;
        m_nWidgetH = nSize;
        
        final int nHalfSize = nSize / 2;

        m_nMargin = (int)MiscUtil.GetPixelFromDP(15);
        if (m_nMargin > nHalfSize / 5)
            m_nMargin = (int)MiscUtil.GetPixelFromDP(4);
        m_nShiftValue = m_nMargin - (int)MiscUtil.GetPixelFromDP(3);
        
        m_nCenterX = (m_nWidgetW - nSize) / 2 + nHalfSize;
        m_nCenterY = (m_nWidgetH - nSize) / 2 + nHalfSize;
        m_nRadius = nHalfSize - m_nMargin;
    
        m_rc = new RectF(m_nCenterX - m_nRadius, m_nCenterY - m_nRadius, 
                         m_nCenterX + m_nRadius, m_nCenterY + m_nRadius);
        
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "_SizeChanged(): w,h=" + m_nWidgetW + "," + m_nWidgetH + ", r=" + m_nRadius);
    }
    
    private int
    _GetPie(float fX, float fY)
    {
        final int N = m_fPercentages.size();
        if (N <= 0) return -1;

        double dx = Math.atan2(fY - m_nCenterY, fX - m_nCenterX);
        float dxDegree = (float)(dx / (2 * Math.PI) * 360);
        dxDegree = (dxDegree + 90 + 360) % 360;
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "dxDegree=" + dxDegree);

        double dxRatio = ((dxDegree - m_fStartAngle + 360) % 360) / 360;
        
        for (int i = 0; i < N; i++) {
            dxRatio -= m_fPercentages.get(i);
            if (dxRatio <= 0) return i;
        }
        return -1;
    }
    
    private void
    _GetData()
    {
        if (m_fPercentages != null) return;
        if (m_adapter == null) return;
        
        final int N = m_adapter.GetCount();
        
        m_nActivePieIdx = m_adapter.GetActivePieIdx();
        
        m_fPercentages = new ArrayList<Float>(N);
        m_nFillColors = new ArrayList<Integer>(N);
        m_nBorderColors = new ArrayList<Integer>(N);
        
        for (int i = 0; i < N; i++) {
            m_fPercentages.add(m_adapter.GetPercentage(i));
            int nColor = m_adapter.GetColor(i);
            m_nFillColors.add(nColor);
            m_nBorderColors.add(_GetDarkColor(nColor));
        }
        
        if (N > 1) {
            m_fStartAngle = -_PercentageToAngle(m_fPercentages.get(0)) / 2;
        }
        else {
            m_fStartAngle = 0;
        }
    }

    private float
    _PercentageToAngle(float fPercentage)
    {
        return (fPercentage * 360f) % 360;
    }
    
    private void
    _DrawPie(Canvas canvas, float fStartAngle, float fSweepAngle, 
             int nFillColor, int nBorderColor, 
             boolean bSelectedPie, boolean bActivePie) 
    {
        //if (aDebug.ENABLED)
        //    aDebug.d(LOG_TAG, "fStartAngle=" + fStartAngle + ", fSweepAngle=" + fSweepAngle);
        
        m_paintPieFill.setColor(nFillColor);
        
        fStartAngle -= 90;
        
        if (bSelectedPie) {
            canvas.save(Canvas.MATRIX_SAVE_FLAG);
            if (m_fPercentages.size() > 1) {
                //shift the pie
                float fAngle = fStartAngle + fSweepAngle / 2;
                double dxRadius = Math.toRadians((fAngle + 360) % 360);
                float fY = (float)Math.sin(dxRadius);
                float fX = (float)Math.cos(dxRadius);
                canvas.translate(fX * m_nShiftValue, fY * m_nShiftValue);
            }
        }
        
        canvas.drawArc(m_rc, fStartAngle, fSweepAngle, true, m_paintPieFill);
        if (bActivePie)
            canvas.drawArc(m_rc, fStartAngle, fSweepAngle, true, m_paintActivePieFill);
        
        if (bSelectedPie) {
            canvas.drawArc(m_rc, fStartAngle, fSweepAngle, true, m_paintSelectedPieBorder);
            
            canvas.restore();
        }
        else {
            m_paintPieBorder.setColor(nBorderColor);
            canvas.drawArc(m_rc, fStartAngle, fSweepAngle, true, m_paintPieBorder);
        }
    }
    
    private int
    _GetDarkColor(int nColor)
    {
        float[] fHSV = new float[3];
        Color.colorToHSV(nColor, fHSV);
        fHSV[2] *= 0.6f;        
        nColor = Color.HSVToColor(fHSV);
        return Color.argb(0xFF, Color.red(nColor), Color.green(nColor), Color.blue(nColor));
    }       
        
    /////////////////////////////////////////////////
    public static abstract class Adapter {
        private PieChartView    m_vPieChart;
        
        public Adapter() {}
        
        abstract protected int GetCount();
        abstract protected float GetPercentage(int nPos);
        abstract protected int GetColor(int nPos);
        abstract protected int GetActivePieIdx();
        
        public void
        NotifyDataSetChanged()
        {
            m_vPieChart._Init();
            
            m_vPieChart._GetData();
            
            m_vPieChart.postInvalidate();
        }
    }
}
